﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using ADONETDataServicesExtension.BLLUtils;

namespace ADONETDataServicesExtension.Utils
{
    public class ObjectExpressionToEFExpressionConverter : ExpressionVisitor
    {
        private Func<Type, Type> _objectTypeToEFType;
        private Dictionary<ParameterExpression, ParameterExpression> _lambdaParameters;

        protected ObjectExpressionToEFExpressionConverter(Func<Type, Type> objectTypeToEFType)
        {
            _objectTypeToEFType = objectTypeToEFType;
        }

        public static Expression ConvertExpression(Expression expression, Func<Type, Type> objectTypeToEFType)
        {
            return new ObjectExpressionToEFExpressionConverter(objectTypeToEFType).Visit(expression);
        }
        public static Expression ConvertExpression(Expression expression, Type objectType, Type efType)
        {
            return ConvertExpression(expression, entityType =>
                {
                    var objectTypeTmp = objectType;
                    do
                    {
                        if (entityType == objectTypeTmp)
                            return efType;
                    } while ((objectTypeTmp = objectTypeTmp.BaseType) != null);
                    return null;
                });
        }

        public Dictionary<ParameterExpression, ParameterExpression> LambdaParameters
        {
            get
            {
                if (_lambdaParameters == null)
                    _lambdaParameters = new Dictionary<ParameterExpression, ParameterExpression>();
                return _lambdaParameters;
            }
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            EFEntityPropertyAttribute efPropertyAttribute = m.Member.Attribute<EFEntityPropertyAttribute>();
            if (efPropertyAttribute == null)
                return base.VisitMemberAccess(m);
            var efType = _objectTypeToEFType(m.Member.DeclaringType);
            if (efType == null)
                throw new InvalidOperationException();
            return Expression.Property(Visit(m.Expression), efType.GetProperty(efPropertyAttribute.EFPropertyName ?? m.Member.Name));
        }

        protected override Expression VisitParameter(ParameterExpression p)
        {
            if (LambdaParameters.ContainsKey(p))
                return LambdaParameters[p];
            return (ParameterExpression)base.VisitParameter(p);
        }

        protected override Expression VisitLambda(LambdaExpression lambda)
        {
            ParameterExpression[] parameters = new ParameterExpression[lambda.Parameters.Count];
            int parameterIndex = 0;
            foreach (var p in lambda.Parameters)
            {
                var efType = _objectTypeToEFType(p.Type);
                if (p != null && efType != null)
                {
                    var parameter = Expression.Parameter(efType, p.Name);
                    LambdaParameters.Add(p, parameter);
                    parameters[parameterIndex] = parameter;
                }
                else
                    parameters[parameterIndex] = p;
                parameterIndex++;
            }
            Expression body = Visit(lambda.Body);
            if (body != lambda.Body)
                return Expression.Lambda(body, parameters);
            return lambda;
        }
    }
}
